<!--
  Copyright (c) 2021, Zhang Shen
  
  CZ Open Spice Schematic .
-->
<html>
<head>
	<title>CZ Open Spice Schematic </title>

	<!-- Sets the basepath for the library if not in same directory -->
	<script type="text/javascript">
		mxBasePath = 'static/schematic/src';
	</script>

	<!-- Loads and initializes the library -->
	<script type="text/javascript" src="static/schematic/src/js/mxClient.js"></script>
    
    <link href="static/schematic/css/tab.css" rel="stylesheet" type="text/css"/>

    <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-2.2.3.min.js"></script>
    <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.2.3.min.js"></script>
    <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.2.3.min.js"></script>
    <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-api-2.2.3.min.js"></script>
    <script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-api-2.2.3.min.js"></script>
    <script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>



	<!-- Example code -->
	<script type="text/javascript">
		// Program starts here. Creates a sample graph in the
		// DOM node with the specified ID. This function is invoked
		// from the onLoad event handler of the document (see below).
		function main()
		{
			// Defines an icon for creating new connections in the connection handler.
			// This will automatically disable the highlighting of the source vertex.
			// mxConnectionHandler.prototype.connectImage = new mxImage('images/connector.gif', 16, 16);

            // If connect preview is not moved away then getCellAt is used to detect the cell under
            // the mouse if the mouse is over the preview shape in IE (no event transparency), ie.
            // the built-in hit-detection of the HTML document will not be used in this case.
            mxConnectionHandler.prototype.movePreviewAway = false;
            mxConnectionHandler.prototype.waypointsEnabled = true;
            mxGraph.prototype.resetEdgesOnConnect = false;
            mxConstants.SHADOWCOLOR = '#C0C0C0';

            // Replaces the port image
            mxConstraintHandler.prototype.pointImage = new mxImage('static/schematic/images/dot.gif', 10, 10);

            // Enables guides
            mxGraphHandler.prototype.guidesEnabled = true;
            
            // Alt disables guides
            mxGuide.prototype.isEnabledForEvent = function(evt)
            {
                return !mxEvent.isAltDown(evt);
            };
            
            // Enables snapping waypoints to terminals
            mxEdgeHandler.prototype.snapToTerminals = true;            


			// Checks if browser is supported
			if (!mxClient.isBrowserSupported())
			{
				// Displays an error message if the browser is
				// not supported.
				mxUtils.error('Browser is not supported!', 200, false);
			}
			else
			{
				// Creates the div for the toolbar
				var tbContainer = document.createElement('div');
				tbContainer.style.position = 'absolute';
				tbContainer.style.overflow = 'hidden';
				tbContainer.style.padding = '2px';
				tbContainer.style.left = '0px';
				tbContainer.style.top = '26px';
				tbContainer.style.width = '24px';
				tbContainer.style.bottom = '0px';
				
				document.body.appendChild(tbContainer);
			
				// Creates new toolbar without event processing
				var toolbar = new mxToolbar(tbContainer);
				toolbar.enabled = false
				
				// Creates the div for the graph
				container = document.createElement('div');
				container.style.position = 'absolute';
				container.style.overflow = 'hidden';
				container.style.left = '24px';
				container.style.top = '26px';
				container.style.right = '0px';
				container.style.bottom = '0px';
				container.style.background = 'url("static/schematic/images/wires-grid.gif")';

				document.body.appendChild(container);
				
				// Workaround for Internet Explorer ignoring certain styles
				if (mxClient.IS_QUIRKS)
				{
					document.body.style.overflow = 'hidden';
					new mxDivResizer(tbContainer);
					new mxDivResizer(container);
				}
	
				// Creates the model and the graph inside the container
				// using the fastest rendering available on the browser
				var model = new mxGraphModel();
                var graph = new mxGraph(container, model);
                graph.setConnectable(true);
                graph.setPortsEnabled(false);
                graph.setTooltips(true);

                graph.view.scale = 1;
                graph.setPanning(true);
                // graph.setConnectable(true);
                graph.setConnectableEdges(true);
                graph.setDisconnectOnMove(false);
                graph.foldingEnabled = false;
                
                //Maximum size
                // graph.maximumGraphBounds = new mxRectangle(0, 0, 800, 600)
                // graph.border = 50;

                // Panning handler consumed right click so this must be
                // disabled if right click should stop connection handler.
                graph.panningHandler.isPopupTrigger = function() { return false; };
                
                // Enables return key to stop editing (use shift-enter for newlines)
                graph.setEnterStopsCellEditing(true);
                
                // 只显示名字，其他的value进行隐藏
				// Overrides method to provide a cell label in the display
				graph.convertValueToString = function(cell)
				{
                    var Value_raw = cell.getValue();
                    var Value_att = ParseAttribute(Value_raw);
                    var Value_show = '';
                    if(Value_att['Name'] == 'unknown'){
                        if(Value_att['name'] != 'unknown'){
                            Value_show = Value_att['name'];
                        }
                    }
                    else{
                        Value_show = Value_att['Name'];
                    }

                    // var Value_show = Value_att['name'] == 'unknown' ? '' : Value_att['name'];

					return Value_show;
                    
                };
                // 暂时不允许在此进行修改
                var cellLabelChanged = graph.cellLabelChanged;
                graph.cellLabelChanged = function(cell, new_value, autoSize)
                {

                    // // Clones the value for correct undo/redo
                    // var elt = cell.value.cloneNode(true);

                    // newValue = elt;
                    // autoSize = false;

					// cellLabelChanged.apply(this, arguments);
                };

				graph.getTooltipForCell = function(cell)
				{
					return 'Doubleclick and right- or shiftclick';
                }
				// Installs a popupmenu handler using local function (see below).
				graph.popupMenuHandler.factoryMethod = function(menu, cell, evt)
				{
					return createPopupMenu(graph, menu, cell, evt);
                };
                                
				// Stops editing on enter or escape keypress
				// var keyHandler = new mxKeyHandler(graph);
				var rubberband = new mxRubberband(graph);
                
                // Add Vertex
				// var addVertex = function(icon, w, h, style)
				// {
				// 	var vertex = new mxCell(null, new mxGeometry(0, 0, w, h), style);
				// 	vertex.setVertex(true);
				
				// 	addToolbarItem(graph, toolbar, vertex, icon);
                // };
				var addVertex_2 = function(icon, w, h, style,value)
				{
					var vertex = new mxCell(null, new mxGeometry(0, 0, w, h), style);
                    vertex.setVertex(true);
                    vertex.setValue(value);
				
					addToolbarItem(graph, toolbar, vertex, icon);
                };	

                addVertex_2('static/schematic/icon_ee/res.ico', 80, 30, 'shape=resistor;verticalLabelPosition=top;verticalAlign=bottom','Name=R;Value=10Ohm;');
                addVertex_2('static/schematic/icon_ee/cap.ico', 40, 30, 'shape=capacitor;verticalLabelPosition=top;verticalAlign=bottom','Name=C;Value=10pF;');
                addVertex_2('static/schematic/icon_ee/nmos.ico', 60, 60, 'shape=n_mosfet;verticalLabelPosition=top;verticalAlign=bottom','Name=M_NMOS;Model=CMOSN_180nm;Length=1um;Width=20um;');
                addVertex_2('static/schematic/icon_ee/pmos.ico', 60, 60, 'shape=p_mosfet;verticalLabelPosition=top;verticalAlign=bottom','Name=M_PMOS;Model=CMOSP_180nm;Length=1um;Width=20um;');
                addVertex_2('static/schematic/icon_ee/vdd.ico', 40, 30, 'shape=vdd;verticalLabelPosition=top;verticalAlign=bottom','Name=Vdd;');
                addVertex_2('static/schematic/icon_ee/gnd.ico', 40, 40, 'shape=gnd;verticalLabelPosition=bottom;verticalAlign=top','Name=Gnd;');
                addVertex_2('static/schematic/icon_ee/pin.ico', 40, 40, 'shape=pin;verticalLabelPosition=top;verticalAlign=bottom','Name=Pin;');
                addVertex_2('static/schematic/icon_ee/pout.ico', 40, 40, 'shape=pout;verticalLabelPosition=top;verticalAlign=bottom','Name=Pout;');
                addVertex_2('static/schematic/icon_ee/vdc.ico', 40, 40, 'shape=vdc;labelPosition=left;align=right','Name=Vdc;DC=1 V;');
                addVertex_2('static/schematic/icon_ee/vac.ico', 40, 40, 'shape=vac;labelPosition=left;align=right','Name=Vac;DC=0 V;ACMAG=1 V;ACPHASE=0 deg;');
                addVertex_2('static/schematic/icon_ee/vsin.ico', 40, 40, 'shape=vsin;labelPosition=left;align=right','Name=Vsin;V0=0 V;VA=1 V;FREQ=1k Hz;TD=0 ;THEAT=0 ;PHASE=0 ;');
                addVertex_2('static/schematic/icon_ee/vpulse.ico', 40, 40, 'shape=vpulse;labelPosition=left;align=right','Name=Vpulse;V1=0 V;V2=1 V;TD=0u s;TR=0u s;TF=0u s;PW=10u s;PER=20u s;');
                
              
				toolbar.addLine();
                
                click_handler(graph);
                ports_handler(graph);
                style_handler(graph);
                Clipboard_Handler(graph);
                bottom_bar(graph);

                // create a window
                // var lorem_1 = "Welcom to use CZ_SPICE,here is the user help manual !";
                // var lorem_2 = "To be continued......";
                var attention_head = "注意事项（请务必阅读！！！）:";
                var attention_body = ["1.当前系统不会自动更新元件名，当引用多个相同元件时，请在执行仿真前仔细检查元件的名字是否有重复，如有重复请自行在属性中修改元件名。（Vdd和Gnd除外）元件名字重复的情况下，仿真会失败！！！",
                                      "2.由于在后台CMOS器件统一使用了180nm的工艺库文件，所以请不要修改CMOS器件的MODEL。另外，请注意，180nm工艺库下的电源电压为3.3V！！！！",
                                      "3.当在属性中修改元件名后，显示界面可能不会立即更新新名字，移动元件即可更新。不移动元件也不会对仿真有影响",
                                      "4.绘制结果时，请先点击Refresh来更新仿真杰作，然后再点击Plot进行绘图。当前绘图界面会把所有节点的电压数据绘制出来。更加灵活的可以选择节点的绘图功能将在后续添加。",
                                      "5.系统尚不成熟，如遇bug，可以将bug的相关情况发送到colson_zhang@foxmail.com。"];
            

				var content = document.createElement('div');
                
                var the_head = document.createElement('h3');
                var the_term = document.createTextNode(attention_head);
                the_head.appendChild(the_term);
                content.appendChild(the_head);
                for (var i =0 ;i<attention_body.length; i++){
                    var paragraph  = document.createElement('p');
                    var the_term = document.createTextNode(attention_body[i]);
                    paragraph.appendChild(the_term);
                    content.appendChild(paragraph);
                }
                
                // mxUtils.writeln(content, lorem_1);
                // mxUtils.writeln(content, lorem_2);
				wnd = new mxWindow('Help', content, 520, 50, 300, 250, true, true);
				wnd.setMaximizable(true);
				wnd.setScrollable(true);
				wnd.setResizable(true);
                wnd.setVisible(true);
                wnd.setClosable(true);
                
                showOutline(graph);

				// mxLog.show();
			}
		}
</script>

<script type="text/javascript">
// 获取属性
function ParseAttribute(value)
{
    value = value||"Name=unknown;" ;
    // value = value.toLowerCase();
    var attribute = {} ;
    v = value.replace(" ","");
    var v_split = v.split(";");
    for(var i = 0; i<v_split.length; i++)
    {
        if(v_split[i].length > 0)
        {
            var pro = v_split[i].split("=");
            attribute[pro[0]] = pro[1];
        }
    }
    return attribute;
};

// 初步从xml中提取有用信息
function ParseSpice(circuit_xml){
    var encoder = new mxCodec();
    var node = encoder.encode(circuit_xml);
    var content = "";
    var term = "";

    let reg_1 = /<mxcell.+?\/>/ig;
    let reg_2 = /<mxcell.+?\/mxcell>/ig;
    let reg_3 = /<mxGeometry.+?\/>/ig;
    let reg_4 = /<mxGeometry.+?<\/mxGeometry>/ig;
    let reg_5 = /vertex="1"/ig;

    let reg_id = /id="(.*?)"/i;
    let reg_value = /value="(.+?)"/i;
    let reg_style = /style="(.+?)"/i;
    let reg_parent = /parent="(.+?)"/i;

    let reg_edge = /edge="(.+?)"/i;
    let reg_source = /source="(.+?)"/i;
    let reg_target = /target="(.+?)"/i;


    let element_list = [];
    let wires_list = [];
    let element = [];
    let wires = [];
    let result = {};

    content += mxUtils.getXml(node);
    // remove heading text
    term = content.match(reg_1);
    content = content.replace(term[0],'');
    content = content.replace(term[1],'');
    // remove each element_list's geometry
    term = content.match(reg_2);
    for(var i=0 ; i<term.length; i++){
        var geometry = term[i].match(reg_4);
        if(geometry != null){
            term[i] = term[i].replace(geometry,'');
        }
        else{
            geometry = term[i].match(reg_3);
            term[i] = term[i].replace(geometry,'');
        }
    }
    // remove some text
    for(var i=0; i<term.length; i++){
        term[i] = term[i].replace("<mxCell","");
        term[i] = term[i].replace("</mxCell>","");
        term[i] = term[i].replace(">","");
    }

    // divide term into element_list and wires_list
    for(var i=0 ; i<term.length; i++){
        var vertex = term[i].match(reg_5);
        if(vertex != null){
            term[i] = term[i].replace(vertex,"")
            element_list.push(term[i]);
        }
        else{
            wires_list.push(term[i]);
        }
    }
    // full elements
    for(var i=0; i<element_list.length; i++){
        let ele = {};
        let ele_v = {};
        let ele_s = {};
        let temp_ele ;
        // get id
        ele['id']     = element_list[i].match(reg_id)[1] ;
        // get value
        temp_ele = element_list[i].match(reg_value)[1] ;
        temp_ele = temp_ele.split(";");
        for(var j=0; j<temp_ele.length ;j++){
            let tem = temp_ele[j].split("=");
            ele_v[tem[0]] = tem[1];
        }
        ele['value']  = ele_v ;
        // get style
        temp_ele = element_list[i].match(reg_style)[1] ;
        temp_ele = temp_ele.split(";");
        for(var j=0; j<temp_ele.length; j++){
            let tem = temp_ele[j].split("=");
            ele_s[tem[0]] = tem[1];
        }
        ele['style']  = ele_s ;
        // get parent
        ele['parent'] = element_list[i].match(reg_parent)[1] ;

        element.push(ele);
    }
    // full wires
    for(var i=0; i<wires_list.length ; i++){
        let ele = {};
        let ele_s = {};
        let temp_ele ;

        // get id
        ele['id'] = wires_list[i].match(reg_id)[1] ;

        // get style
        temp_ele = wires_list[i].match(reg_style)[1] ;
        temp_ele = temp_ele.split(";");
        for(var j=0; j<temp_ele.length; j++){
            let tem = temp_ele[j].split("=");
            ele_s[tem[0]] = tem[1];
        }
        ele['style']  = ele_s ;

        // get edge
        ele['edge'] = wires_list[i].match(reg_edge)[1];

        // get source
        ele['source'] = wires_list[i].match(reg_source)[1];

        // get target
        ele['target'] = wires_list[i].match(reg_target)[1];

        wires.push(ele);
    }

    result['elements'] = element;
    result['wires'] = wires;

    return result ;
};

function NormalizeXML(info){
    let circuit = [];
    let netlist = [];
    let nodes = [];

    for(var i=0; i<info['elements'].length; i++)
    {
        let temp = {};
        temp['id'] = info['elements'][i]['id'];
        temp['name'] = info['elements'][i]['value']['Name'];
        temp['type'] = info['elements'][i]['style']['shape'];
        if(temp['type'] == 'vdd'){
            temp['port'] = {'vdd': 'vdd'};
            temp['port_keys'] = ['vdd'];
        }
        else if(temp['type'] == 'gnd'){
            temp['port'] = {'gnd': 'gnd'};
            temp['port_keys'] = ['gnd'];
        }
        else if(temp['type'] == 'n_mosfet' || temp['type'] == 'p_mosfet'){
            temp['port'] = {'drain': 'd', 'gate': 'g', 'source': 's', 'bulk': 's' };
            temp['port_keys'] = ['drain','gate','source','bulk'];
            temp['value'] = {'model' : info['elements'][i]['value']['Model'],
                             'length': info['elements'][i]['value']['Length'],
                             'width' : info['elements'][i]['value']['Width']};
        }
        else if(temp['type'] == 'resistor'){
            temp['port'] = {'pos':'pos', 'neg':'neg'};
            temp['port_keys'] = ['pos','neg'];
            temp['value'] = {'value': info['elements'][i]['value']['Value']}
        }
        else if(temp['type'] == 'capacitor'){
            temp['port'] = {'pos':'pos', 'neg':'neg'};
            temp['port_keys'] = ['pos','neg'];
            temp['value'] = {'value': info['elements'][i]['value']['Value']}
        }
        else if(temp['type'] == 'pin'){
            temp['port'] = {'pin': 'pin'};
            temp['port_keys'] = ['pin'];
        }
        else if(temp['type'] == 'pout'){
            temp['port'] = {'pout': 'pout'};
            temp['port_keys'] = ['pout'];
        }
        else if(temp['type'] == 'vdc'){
            temp['port'] = {'pos': 'pos', 'neg':'neg'};
            temp['port_keys'] = ['pos','neg'];
            temp['value'] = {'DC': info['elements'][i]['value']['DC'] }
        }
        else if(temp['type'] == 'vac'){
            temp['port'] = {'pos': 'pos', 'neg':'neg'};
            temp['port_keys'] = ['pos','neg'];
            temp['value'] = {'ACMAG': info['elements'][i]['value']['ACMAG'],
                             'ACPHASE': info['elements'][i]['value']['ACPHASE'],
                             'DC':info['elements'][i]['value']['DC']}
        }
        else if(temp['type'] == 'vsin'){
            temp['port'] = {'pos': 'pos', 'neg':'neg'};
            temp['port_keys'] = ['pos','neg'];
            temp['value'] = {'V0': info['elements'][i]['value']['V0'],
                             'VA': info['elements'][i]['value']['VA'],
                             'FREQ': info['elements'][i]['value']['FREQ'],
                             'TD': info['elements'][i]['value']['TD'],
                             'THEAT': info['elements'][i]['value']['THEAT'],
                             'PHASE': info['elements'][i]['value']['PHASE']
                            }            
        }
        else if(temp['type'] == 'vpulse'){
            temp['port'] = {'pos': 'pos', 'neg':'neg'};
            temp['port_keys'] = ['pos','neg'];
            temp['value'] = {'V1': info['elements'][i]['value']['V1'],
                             'V2': info['elements'][i]['value']['V2'],
                             'TD': info['elements'][i]['value']['TD'],
                             'TR': info['elements'][i]['value']['TR'],
                             'TF': info['elements'][i]['value']['TF'],
                             'PW': info['elements'][i]['value']['PW'],
                             'PER': info['elements'][i]['value']['PER']
                            }            
        }
        circuit.push(temp);
    }

    for(var i=0; i<info['wires'].length; i++)
    {
        let temp = {};
        temp['id'] = info['wires'][i]['id'];
        temp['node_source'] = info['wires'][i]['source'] +" "+ info['wires'][i]['style']['sourcePort'];
        temp['node_target'] = info['wires'][i]['target'] +" ";
        if(info['wires'][i]['style']['targetPort'] != null){
            temp['node_target'] += info['wires'][i]['style']['targetPort'];
        }
        else{
            temp['node_target'] += 'null';
        }
        netlist.push(temp);
    }

    for(var i=0; i<netlist.length; i++){
        let temp = [];
        let index_s = 0;
        let index_t = 0;
        let source = netlist[i]['node_source'];
        let target = netlist[i]['node_target'];
        if(target.indexOf('null') != -1){
            let target_id = target.split(" ")[0];
            let index_id = SearchID(netlist,target_id);
            index_s = SearchExit(nodes,netlist[index_id]['node_source']);
            if(index_s == -1){
                index_t = SearchExit(nodes,netlist[index_id]['node_target']);
                if(index_t == -1){
                    temp.push(source);
                    temp.push(netlist[index_id]['node_source']);
                    temp.push(netlist[index_id]['node_target']);
                    nodes.push(temp);   
                }
                else{
                    nodes[index_t].push(source);                     
                }
            }
            else{
                nodes[index_s].push(source);
            }
        }
        else{
            index_s = SearchExit(nodes,source);
            index_t = SearchExit(nodes,target);
            if(index_s == -1){
                if(index_t == -1){
                    temp.push(source);
                    temp.push(target);
                    nodes.push(temp);
                }
                else{
                    nodes[index_t].push(source);
                }
            }
            else{
                if(index_t == -1){
                    nodes[index_s].push(target);
                }
            }
        }   
    }

    let count = 1 ;
    let node_str = '';
    let flag_gnd = false;
    let flag_vdd = false;
    for(var i=0; i<nodes.length; i++){
        node_str =  String(count);
        for(var j=0; j<nodes[i].length; j++){
            let node_info = nodes[i][j].split(" ");
            let node_id = node_info[0];
            let node_port = node_info[1];
            let node_index = SearchID(circuit,node_id);
            if(node_port == 'gnd'){
                flag_gnd = true;
            }
            else if(node_port == 'vdd'){
                flag_vdd = true ;
            }
            else{
                if(node_index != -1){
                    circuit[node_index]['port'][node_port] = node_str;

                    if(node_port == 'source'){
                        circuit[node_index]['port']['bulk'] = circuit[node_index]['port']['source'];
                    }
                }
            }
        }

        if(flag_gnd){
            flag_gnd = false;
            for(var j=0; j<nodes[i].length; j++){
                let node_info = nodes[i][j].split(" ");
                let node_id = node_info[0];
                let node_port = node_info[1];
                let node_index = SearchID(circuit,node_id);
                node_str = "0";
                if(node_index != -1){
                    circuit[node_index]['port'][node_port] = node_str;

                    if(node_port == 'source'){
                        circuit[node_index]['port']['bulk'] = circuit[node_index]['port']['source'];
                    }
                }
            }
        }
        else if(flag_vdd){
            flag_vdd = false;
            for(var j=0; j<nodes[i].length; j++){
                let node_info = nodes[i][j].split(" ");
                let node_id = node_info[0];
                let node_port = node_info[1];
                let node_index = SearchID(circuit,node_id);
                node_str = "vdd";
                if(node_index != -1){
                    circuit[node_index]['port'][node_port] = node_str;

                    if(node_port == 'source'){
                        circuit[node_index]['port']['bulk'] = circuit[node_index]['port']['source'];
                    }
                }
            }            
        }
        else{
            count += 1;
        }

    }
    return circuit;
};

function SearchID(array,id){
    for(var i=0; i<array.length; i++){
        if(array[i]['id'] == id){
            return i;
        }
    }
    return -1;
}

function SearchExit(array,element){
    for(var i=0; i<array.length; i++){
        if(array[i].includes(element)){
            return i;
        }
    }
    return -1;
};

// 根据从xml提取出来的info生成spice网表
function ExtractSpice(circuit){
    let spice = "";
    let key ;
    for(var i=0; i<circuit.length;i++){
        spice += circuit[i]['name'] + " ";
        for(var j=0; j<circuit[i]['port_keys'].length; j++){
            spice += circuit[i]['port'][circuit[i]['port_keys'][j]] + " ";
        }
        spice += circuit[i]['type'] + " ";
        for(key in circuit[i]['value']){
            spice += circuit[i]['value'][key] + " ";
        }
        spice += "\n";
    }
    return spice;
};
// 根据从xml提取出来的info生成spice网表
function ExtractSpice_2(circuit){
    let spice = "";
    let key ;
    for(var i=0; i<circuit.length;i++){
        if((circuit[i]['type']=='gnd') || (circuit[i]['type']=='vdd')){
            continue;
        }
        else if((circuit[i]['type']=='pin') || (circuit[i]['type']=='pout')){
            continue;
        }
        else if((circuit[i]['type']=='p_mosfet') || (circuit[i]['type']=='n_mosfet')){
            spice += circuit[i]['name'] + " ";
            for(var j=0; j<circuit[i]['port_keys'].length; j++){
                spice += circuit[i]['port'][circuit[i]['port_keys'][j]] + " ";
            }
            spice += circuit[i]['value']['model'] + " ";
            spice += 'l=' + circuit[i]['value']['length']  + " ";
            spice += 'w=' + circuit[i]['value']['width']   + " ";
            spice += "\n";
        }
        else if(circuit[i]['type']=='vdc'){
            spice += circuit[i]['name'] + " ";
            for(var j=0; j<circuit[i]['port_keys'].length; j++){
                spice += circuit[i]['port'][circuit[i]['port_keys'][j]] + " ";
            }
            spice += 'DC' + " ";
            spice += (circuit[i]['value']['DC'].replace(" ","")).replace("V","") + " ";
            spice += "\n";      
        }
        else if(circuit[i]['type']=='vac'){
            spice += circuit[i]['name'] + " ";
            for(var j=0; j<circuit[i]['port_keys'].length; j++){
                spice += circuit[i]['port'][circuit[i]['port_keys'][j]] + " ";
            }
            spice += 'DC' + " ";
            spice += (circuit[i]['value']['DC'].replace(" ","")).replace("V","") + " ";
            spice += 'AC' + " ";
            spice += (circuit[i]['value']['ACMAG'].replace(" ","")).replace("V","") + " ";
            if(circuit[i]['value']['ACPHASE'].replace(" ","").replace("deg","") != "0"){
                spice += circuit[i]['value']['ACPHASE'].replace(" ","").replace("deg","") + " ";
            }
            spice += "\n";
        }
        else if(circuit[i]['type']=='vsin'){
            spice += circuit[i]['name'] + " ";
            for(var j=0; j<circuit[i]['port_keys'].length; j++){
                spice += circuit[i]['port'][circuit[i]['port_keys'][j]] + " ";
            }
            spice += "SIN(" + " ";
            spice += circuit[i]['value']['V0'].replace(" ","").replace("V","") + " ";
            spice += circuit[i]['value']['VA'].replace(" ","").replace("V","") + " ";
            spice += circuit[i]['value']['FREQ'].replace(" ","").replace("Hz","") + " ";
            spice += ")";
            spice += "\n";

        }
        else if(circuit[i]['type']=='vpulse'){
            spice += circuit[i]['name'] + " ";
            for(var j=0; j<circuit[i]['port_keys'].length; j++){
                spice += circuit[i]['port'][circuit[i]['port_keys'][j]] + " ";
            }
            spice += "PULSE(" + " ";
            for(key in circuit[i]['value']){
                spice += circuit[i]['value'][key].replace(" ","") + " ";
            }
            spice += ")";
            spice += "\n";
        }
        else{
            spice += circuit[i]['name'] + " ";
            for(var j=0; j<circuit[i]['port_keys'].length; j++){
                spice += circuit[i]['port'][circuit[i]['port_keys'][j]] + " ";
            }
            // spice += circuit[i]['type'] + " ";
            for(key in circuit[i]['value']){
                spice += circuit[i]['value'][key] + " ";
                // spice += circuit[i]['value'][key] ;
            }
            spice += "\n";
        }
    }
    return spice;
};


function get_spice(graph){

    var node = ParseSpice(graph.getModel()); 
    var circuit = NormalizeXML(node);
    let spice = ExtractSpice_2(circuit);
    return spice;

};

</script>


<!-- Simulator 窗口模式选择 -->
<script> 

function openMode(evt, tabMode, ducument_ID) {
  var i, tabcontent, tablinks;

  var the_document = document.getElementById(ducument_ID);

  tabcontent = the_document.getElementsByClassName("tabcontent");
  for (i = 0; i < tabcontent.length; i++) {
    tabcontent[i].style.display = "none";
  }
  tablinks = the_document.getElementsByClassName("tablinks");
  for (i = 0; i < tablinks.length; i++) {
    tablinks[i].className = tablinks[i].className.replace(" active", "");
  }
  document.getElementById(tabMode).style.display = "block";
  evt.currentTarget.className += " active";

};


</script>


<!-- 向服务器传递数据 -->
<script type="text/javascript">
function ajax_message(data) {
    var data = new Object();
    data["name"] = "XerCis";
    data["message"] = "This is the message from ajax(spice2) !";
    $.ajax({
        type: 'POST',
        url: "/test",
        data: data,
        success: function (response) {
            alert(response);
        }
    });
}

// 由于ajax传递参数时，无法直接进行List的传递，故将属性List转换为了string然后进行传递
function Simulator(sim_type, spice){

    // var properties = [];
    var properties_send = "";
    var sel = $("#"+sim_type+" input");

    // alert(sim_type);

    sel.each(function(){
    var new_element = {};
    var n = $(this)[0].name;
    var v = $(this)[0].value;

    properties_send  += n + "=" + v + ";"; 
    // new_element[n] = v ;
    // properties.push(new_element);
    });

    
    var data = new Object();
    data["sim_type"] = sim_type;
    data["properties"] = properties_send;
    data['spice'] = spice;
    // alert(spice);

    // for(var i=0; i<properties.length; i++){
    //   var key = Object.keys(properties[i])[0];
    //   var val = Object.values(properties[i])[0];
    //   alert(key + " "+ val);
    // }

    $.ajax({
        type: 'POST',
        url: "/simulation",
        data: data,
        success: function (response) {
            alert(response);
        }
    });

};


</script>




<script type="text/javascript">
function showProperties(graph,cell)
{

	cell = cell || graph.getSelectionCell();

	// Uses the root node for the properties dialog
	// if not cell was passed in and no cell is
	// selected
	if (cell == null)
	{
		cell = graph.getCurrentRoot();

		if (cell == null)
		{
			cell = graph.getModel().getRoot();
		}
    }
    
	if (cell != null)
	{
		// Makes sure there is no in-place editor in the
		// graph and computes the location of the dialog
        graph.stopEditing(true);

        var form = new mxForm('properties');
        var id = form.addText('ID', cell.getId());
        id.setAttribute('readonly', 'true');

        var value = cell.getValue();
        var attribute = ParseAttribute(value);
        var Text_attribute = {};
        for(x in attribute){
            Text_attribute[x] = form.addText(x, attribute[x]);
        }
        // var value_cell = form.addText('value', cell.getValue());
        function AssembleAttribute(){
            var Str_attribute = '';
            for(x in attribute){
                Str_attribute += x + '=' + Text_attribute[x].value + ';';
            }
            return Str_attribute ;
        }

        var okFunction = mxUtils.bind(this, function(){
            
            cell.setValue(AssembleAttribute());
            
            // graph.cellLabelChanged(cell,AssembleAttribute(),false);

            wnd.destroy();
            
            // cell.value = value ;
        });

		var cancelFunction = mxUtils.bind(this, function()
		{
			// Hides the dialog
			wnd.destroy();
        });
        
        form.addButtons(okFunction, cancelFunction);
        
        var wnd = new mxWindow(
			'Properties',
            form.table, 800, 320, 200, 150, true,true);
        wnd.setMaximizable(true);
        wnd.setScrollable(true);
        wnd.setResizable(true);
        wnd.setVisible(true);
        wnd.setClosable(true);        

	}
};

</script>


<script type="text/javascript">
function showOutline(graph)
{
	var create = true;

	if (create)
	{
		var div = document.createElement('div');

		div.style.overflow = 'hidden';
		div.style.position = 'relative';
		div.style.width = '100%';
		div.style.height = '100%';
		div.style.background = 'white';
		div.style.cursor = 'move';

		if (document.documentMode == 8)
		{
			div.style.filter = 'progid:DXImageTransform.Microsoft.alpha(opacity=100)';
		}

		var wnd = new mxWindow(
			'Outline',
			div, 1000, 20, 200, 150, false);

		// Creates the outline in the specified div
		// and links it to the existing graph
        var outline = new mxOutline(graph, div);
        wnd.setMaximizable(true);
        wnd.setScrollable(true);
        wnd.setVisible(true);
		wnd.setClosable(true);
		wnd.setResizable(true);
		wnd.destroyOnClose = false;

		wnd.addListener(mxEvent.RESIZE_END, function()
		{
			outline.update();
		});

		this.outline = wnd;
		this.outline.outline = outline;
	}

	// Finally shows the outline
	this.outline.setVisible(true);
	this.outline.outline.update(true);
};

</script>

<script type="text/javascript">

    function addToolbarItem(graph, toolbar, prototype, image)
		{
			// Function that is executed when the image is dropped on
			// the graph. The cell argument points to the cell under
			// the mousepointer if there is one.
			var funct = function(graph, evt, cell)
			{
				graph.stopEditing(false);

				var pt = graph.getPointForEvent(evt);
				var vertex = graph.getModel().cloneCell(prototype);
				vertex.geometry.x = pt.x;
				vertex.geometry.y = pt.y;
				
				graph.setSelectionCells(graph.importCells([vertex], 0, 0, cell));
			}

			// Creates the image which is used as the drag icon (preview)
			var img = toolbar.addMode(null, image, funct);
			mxUtils.makeDraggable(img, graph, funct);
		}

</script>


<script type="text/javascript">
    // Function to create the entries in the popupmenu
    function createPopupMenu(graph, menu, cell, evt)
    {
        if (cell != null)
        {
            menu.addItem('Properities', 'static/schematic/editors/images/image.gif', function()
            {
                showProperties(graph,cell);
            });
        }
        else
        {
            menu.addItem('No-Cell Item', 'static/schematic/editors/images/image.gif', function()
            {
                mxUtils.alert('MenuItem2');
            });
        }
        menu.addSeparator();
        menu.addItem('MenuItem3', 'static/schematic/src/images/warning.gif', function()
        {
            mxUtils.alert('MenuItem3: '+graph.getSelectionCount()+' selected');
        });
    };
</script>


<!-- click listener -->
<script type="text/javascript">
    function click_handler(graph){
        // Installs a handler for click events in the graph
        // that toggles the overlay for the respective cell
        graph.addListener(mxEvent.CLICK, function(sender, evt)
        {
            // var cell = evt.getProperty('cell');
            
            // if (cell != null)
            // {
            //     var overlays = graph.getCellOverlays(cell);
                
            //     if (overlays == null)
            //     {
            //         // Creates a new overlay with an image and a tooltip
            //         var overlay = new mxCellOverlay(
            //             new mxImage('editors/images/overlays/check.png', 16, 16),
            //             'Overlay tooltip');

            //         // Installs a handler for clicks on the overlay							
            //         overlay.addListener(mxEvent.CLICK, function(sender, evt2)
            //         {
            //             mxUtils.alert('Overlay clicked');
            //         });
                    
            //         // Sets the overlay for the cell in the graph
            //         graph.addCellOverlay(cell, overlay);
            //     }
            //     else
            //     {
            //         graph.removeCellOverlays(cell);
            //     }
            // }
        });
        
        // Installs a handler for double click events in the graph
        // that shows an alert box
        graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt)
        {
            var cell = evt.getProperty('cell');
            showProperties(graph,cell);
            // var cell = evt.getProperty('cell');
            // mxUtils.alert('Doubleclick: '+((cell != null) ? 'Cell' : 'Graph'));
            // evt.consume();
        });
    }	

</script>

<script type="text/javascript">
    function bottom_bar(graph){
            document.body.appendChild(mxUtils.button('Zoom In', function()
			{
				graph.zoomIn();
			}));
			
			document.body.appendChild(mxUtils.button('Zoom Out', function()
			{
				graph.zoomOut();
            }));
            
			document.body.appendChild(mxUtils.button('Zoom Actual', function()
			{
				graph.zoomActual();
			}));
			// Undo/redo
			var undoManager = new mxUndoManager();
			var listener = function(sender, evt)
			{
				undoManager.undoableEditHappened(evt.getProperty('edit'));
			};
			graph.getModel().addListener(mxEvent.UNDO, listener);
			graph.getView().addListener(mxEvent.UNDO, listener);
			
			document.body.appendChild(mxUtils.button('Undo', function()
			{
				undoManager.undo();
			}));
			
			document.body.appendChild(mxUtils.button('Redo', function()
			{
				undoManager.redo();
			}));

			document.body.appendChild(mxUtils.button('Delete', function()
			{
				graph.removeCells();
			}));
            document.body.appendChild(mxUtils.button('FlipH', function()
            {
                graph.toggleCellStyles(mxConstants.STYLE_FLIPH);
            }));
            
            document.body.appendChild(mxUtils.button('FlipV', function()
            {
                graph.toggleCellStyles(mxConstants.STYLE_FLIPV);
            }));
            document.body.appendChild(mxUtils.button('Rotate', function()
            {
                var cell = graph.getSelectionCell();
                
                if (cell != null)
                {
                    var geo = graph.getCellGeometry(cell);

                    if (geo != null)
                    {
                        graph.getModel().beginUpdate();
                        try
                        {
                            // Rotates the size and position in the geometry
                            geo = geo.clone();
                            geo.x += geo.width / 2 - geo.height / 2;
                            geo.y += geo.height / 2 - geo.width / 2;
                            var tmp = geo.width;
                            geo.width = geo.height;
                            geo.height = tmp;
                            graph.getModel().setGeometry(cell, geo);
                            
                            // Reads the current direction and advances by 90 degrees
                            var state = graph.view.getState(cell);
                            
                            if (state != null)
                            {
                                var dir = state.style[mxConstants.STYLE_DIRECTION] || 'east'/*default*/;
                                
                                if (dir == 'east')
                                {
                                    dir = 'south';
                                }
                                else if (dir == 'south')
                                {
                                    dir = 'west';
                                }
                                else if (dir == 'west')
                                {
                                    dir = 'north';
                                }
                                else if (dir == 'north')
                                {
                                    dir = 'east';
                                }
                                
                                graph.setCellStyles(mxConstants.STYLE_DIRECTION, dir, [cell]);
                            }
                        }
                        finally
                        {
                            graph.getModel().endUpdate();
                        }
                    }
                }
            }));

			document.body.appendChild(mxUtils.button('Show info', function()
			{
                var node = ParseSpice(graph.getModel()); 
                var circuit = NormalizeXML(node);
                let spice = ExtractSpice(circuit);
                mxUtils.popup(spice, true);
            }));

			document.body.appendChild(mxUtils.button('Show Spice', function()
			{
                
                var node = ParseSpice(graph.getModel()); 
                var circuit = NormalizeXML(node);
                let spice = ExtractSpice_2(circuit);
                mxUtils.popup(spice, true);
            }));

			document.body.appendChild(mxUtils.button('Send Spice', function()
			{
                var node = ParseSpice(graph.getModel()); 
                var circuit = NormalizeXML(node);
                let spice = ExtractSpice_2(circuit);                                                            

                var data = new Object();
                data["name"] = "Spice";
                data["spice"] = spice;
                $.ajax({
                    type: 'POST',
                    url: "/spice",
                    data: data,
                    success: function (response) {
                        alert(response);
                    }
                });

            }));

            document.body.appendChild(mxUtils.button('simulator', function(){

                // let spice = get_spice(graph);
                
                var frame = document.createElement('div');
                frame.setAttribute('id','simulator');

                // var x = document.createElement('script');
                // x.setAttribute('src','static/spice/bokeh_02.js');
                // frame.appendChild(x);

                var div_tab = document.createElement('div');
                div_tab.setAttribute('class','tab');


                var sim_mode = ['transient', 'dc', 'ac'];
                
                var sim_mode_content = {
                    'transient': {
                        "key"   :[ 'step_time', 'end_time', 'start_time','max_time','use_initial_condition'],
                        "value" :[ '0.1 us', '20 us', '0', 'None', 'False']
                        },
                    'dc': {
                        "key"   :[ "src","start","stop","step"],
                        "value" :[ "Vdc","0","3.3","0.1"]
                        },
                    'ac': {
                        "key"   :[ "variation", "number_of_points", "start_frequency", "stop_frequency"],
                        "value" :[ "dec","10","1 Hz","1 MHz"]
                        }
                };
                
                for(var i=0; i<sim_mode.length; i++){
                    var div_mode =  document.createElement('button');
                    div_mode.setAttribute('class',"tablinks");
                    div_mode.setAttribute('onclick',"openMode(event, '"+ sim_mode[i] +"' ,'simulator')");

                    div_mode.innerHTML = sim_mode[i] ;
                    div_tab.appendChild(div_mode);
                }

                frame.appendChild(div_tab);

                for(var i=0; i<sim_mode.length; i++){
                    var div_mode =  document.createElement('div');
                    div_mode.setAttribute('class',"tabcontent");
                    div_mode.setAttribute('id',sim_mode[i]);

                    var div_h3 = document.createElement('h3');
                    div_h3.innerHTML = sim_mode[i];
                    div_mode.appendChild(div_h3);

                    var table = document.createElement('table');

                    var the_mode = sim_mode_content[sim_mode[i]];
                    for(var j=0; j<the_mode['key'].length; j++ ){
                        var form_div = document.createElement('tr');

                        var td_name = document.createElement('td');
                        td_name.innerHTML = the_mode['key'][j] ;
                        form_div.appendChild(td_name);

                        var td_input = document.createElement('td');

                        var input = document.createElement('input');
                        input.setAttribute('name', the_mode['key'][j]);
                        input.setAttribute("class","simulator_input");
                        input.setAttribute('type', "text");
                        input.setAttribute('value', the_mode['value'][j] );
                        td_input.appendChild(input);

                        form_div.appendChild(td_input);

                        table.appendChild(form_div);
                    }

                    var confirm = document.createElement('button');
                    confirm.setAttribute("type","submit");
                    confirm.setAttribute("class",sim_mode[i]);
                    // confirm.setAttribute('onclick',"Simulator('"+sim_mode[i] +"'; ");
                    confirm.onclick = function(){ 
                        let spice = get_spice(graph);
                        Simulator(this.className,spice);
                    };
                    confirm.innerHTML = "Confirm";

                    table.appendChild(confirm);

                    div_mode.appendChild(table);

                    frame.appendChild(div_mode);                    
                };
                


                var w = document.body.clientWidth;
                var h = (document.body.clientHeight || document.documentElement.clientHeight);
                var wnd = new mxWindow('Title', frame, (w-300)/2, (h-300)/3, 400, 400, true, true);

                wnd.setVisible(true);
                wnd.setScrollable(true);
                wnd.setResizable(true);
                wnd.setVisible(true);
                wnd.setClosable(true); 

            }));

			document.body.appendChild(mxUtils.button('show result', function()
			{

                var frame = document.createElement('div');
                frame.setAttribute('id','show_result');

                var x = document.createElement('script');
                x.setAttribute('src','static/spice/show_result2.js');

                frame.appendChild(x);

                var w = document.body.clientWidth;
                var h = (document.body.clientHeight || document.documentElement.clientHeight);
                var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 700, 500, true, true);

                wnd.setVisible(true);
                wnd.setScrollable(true);
                wnd.setResizable(true);
                wnd.setVisible(true);
                wnd.setClosable(true); 

            }));



			// Wire-mode
			var checkbox = document.createElement('input');
			checkbox.setAttribute('type', 'checkbox');
			
			document.body.appendChild(checkbox);
			mxUtils.write(document.body, 'Wire Mode');
			
			// Starts connections on the background in wire-mode
			var connectionHandlerIsStartEvent = graph.connectionHandler.isStartEvent;
			graph.connectionHandler.isStartEvent = function(me)
			{
				return checkbox.checked || connectionHandlerIsStartEvent.apply(this, arguments);
			};
			
			// Avoids any connections for gestures within tolerance except when in wire-mode
			// or when over a port
			var connectionHandlerMouseUp = graph.connectionHandler.mouseUp;
			graph.connectionHandler.mouseUp = function(sender, me)
			{
				if (this.first != null && this.previous != null)
				{
					var point = mxUtils.convertPoint(graph.container, me.getX(), me.getY());
					var dx = Math.abs(point.x - this.first.x);
					var dy = Math.abs(point.y - this.first.y);

					if (dx < graph.tolerance && dy < graph.tolerance)
					{
						// Selects edges in non-wire mode for single clicks, but starts
						// connecting for non-edges regardless of wire-mode
						if (!checkbox.checked && graph.getModel().isEdge(this.previous.cell))
						{
							this.reset();
						}
						
						return;
					}
				}
				
				connectionHandlerMouseUp.apply(this, arguments);
			};
			
			// Grid
			var checkbox2 = document.createElement('input');
			checkbox2.setAttribute('type', 'checkbox');
			checkbox2.setAttribute('checked', 'true');
			
			document.body.appendChild(checkbox2);
			mxUtils.write(document.body, 'Grid');
			
			mxEvent.addListener(checkbox2, 'click', function(evt)
			{
				if (checkbox2.checked)
				{
					container.style.background = 'url(\'static/schematic/images/wires-grid.gif\')';
				}
				else
				{
					container.style.background = '';
				}
				
				container.style.backgroundColor = (invert) ? 'black' : 'white';
			});
			
			mxEvent.disableContextMenu(container);
    };

</script>


<script type="text/javascript">
    function style_handler(graph){

        // Switch for black background and bright styles
        var invert = false;
        
        if (invert)
        {
            container.style.backgroundColor = 'black';
            
            // White in-place editor text color
            mxCellEditorStartEditing = mxCellEditor.prototype.startEditing;
            mxCellEditor.prototype.startEditing = function (cell, trigger)
            {
                mxCellEditorStartEditing.apply(this, arguments);
                
                if (this.textarea != null)
                {
                    this.textarea.style.color = '#FFFFFF';					
                }
            };
            
            mxGraphHandler.prototype.previewColor = 'white';
        }        
        var joinNodeSize = 7;
        var strokeWidth = 2;
        var labelBackground = (invert) ? '#000000' : '#FFFFFF';
        var fontColor = (invert) ? '#FFFFFF' : '#000000';
        var strokeColor = (invert) ? '#C0C0C0' : '#000000';
        var fillColor = (invert) ? 'none' : '#FFFFFF';
        
        var style = graph.getStylesheet().getDefaultEdgeStyle();
        delete style['endArrow'];
        style['strokeColor'] = strokeColor;
        style['labelBackgroundColor'] = labelBackground;
        style['edgeStyle'] = 'wireEdgeStyle';
        style['fontColor'] = fontColor;
        style['fontSize'] = '9';
        style['movable'] = '0';
        style['strokeWidth'] = strokeWidth;
        //style['rounded'] = '1';
        
        // Sets join node size
        style['startSize'] = joinNodeSize;
        style['endSize'] = joinNodeSize;
        
        style = graph.getStylesheet().getDefaultVertexStyle();
        style['gradientDirection'] = 'south';
        //style['gradientColor'] = '#909090';
        style['strokeColor'] = strokeColor;
        //style['fillColor'] = '#e0e0e0';
        style['fillColor'] = 'none';
        style['fontColor'] = fontColor;
        style['fontStyle'] = '1';
        style['fontSize'] = '12';
        style['resizable'] = '0';
        style['rounded'] = '1';
        style['strokeWidth'] = strokeWidth;


    }
</script>


<script type="text/javascript">
    // Computes the position of edge to edge connection points.
    mxGraphView.prototype.updateFixedTerminalPoint = function(edge, terminal, source, constraint)
    {
        var pt = null;
        
        if (constraint != null)
        {
            pt = this.graph.getConnectionPoint(terminal, constraint);
        }

        if (source)
        {
            edge.sourceSegment = null;
        }
        else
        {
            edge.targetSegment = null;
        }
        
        if (pt == null)
        {
            var s = this.scale;
            var tr = this.translate;
            var orig = edge.origin;
            var geo = this.graph.getCellGeometry(edge.cell);
            pt = geo.getTerminalPoint(source);

            // Computes edge-to-edge connection point
            if (pt != null)
            {
                pt = new mxPoint(s * (tr.x + pt.x + orig.x),
                                 s * (tr.y + pt.y + orig.y));
                
                // Finds nearest segment on edge and computes intersection
                if (terminal != null && terminal.absolutePoints != null)
                {
                    var seg = mxUtils.findNearestSegment(terminal, pt.x, pt.y);

                    // Finds orientation of the segment
                    var p0 = terminal.absolutePoints[seg];
                    var pe = terminal.absolutePoints[seg + 1];
                    var horizontal = (p0.x - pe.x == 0);
                    
                    // Stores the segment in the edge state
                    var key = (source) ? 'sourceConstraint' : 'targetConstraint';
                    var value = (horizontal) ? 'horizontal' : 'vertical';
                    edge.style[key] = value;
                    
                    // Keeps the coordinate within the segment bounds
                    if (horizontal)
                    {
                        pt.x = p0.x;
                        pt.y = Math.min(pt.y, Math.max(p0.y, pe.y));
                        pt.y = Math.max(pt.y, Math.min(p0.y, pe.y));
                    }
                    else
                    {
                        pt.y = p0.y;
                        pt.x = Math.min(pt.x, Math.max(p0.x, pe.x));
                        pt.x = Math.max(pt.x, Math.min(p0.x, pe.x));
                    }
                }
            }
            // Computes constraint connection points on vertices and ports
            else if (terminal != null && terminal.cell.geometry.relative)
            {
                pt = new mxPoint(this.getRoutingCenterX(terminal),
                    this.getRoutingCenterY(terminal));
            }

            // Snaps point to grid
            /*if (pt != null)
            {
                var tr = this.graph.view.translate;
                var s = this.graph.view.scale;
                
                pt.x = (this.graph.snap(pt.x / s - tr.x) + tr.x) * s;
                pt.y = (this.graph.snap(pt.y / s - tr.y) + tr.y) * s;
            }*/
        }

        edge.setAbsoluteTerminalPoint(pt, source);
    };
</script>
<!--  
Overrides methods to preview and create new edges.
-->
<script type="text/javascript">
    // Sets source terminal point for edge-to-edge connections.
    mxConnectionHandler.prototype.createEdgeState = function(me)
    {
        var edge = this.graph.createEdge();
        
        if (this.sourceConstraint != null && this.previous != null)
        {
            edge.style = mxConstants.STYLE_EXIT_X+'='+this.sourceConstraint.point.x+';'+
                mxConstants.STYLE_EXIT_Y+'='+this.sourceConstraint.point.y+';';
        }
        else if (this.graph.model.isEdge(me.getCell()))
        {
            var scale = this.graph.view.scale;
            var tr = this.graph.view.translate;
            var pt = new mxPoint(this.graph.snap(me.getGraphX() / scale) - tr.x,
                    this.graph.snap(me.getGraphY() / scale) - tr.y);
            edge.geometry.setTerminalPoint(pt, true);
        }
        
        return this.graph.view.createState(edge);
    };
    
    // Uses right mouse button to create edges on background (see also: lines 67 ff)
    mxConnectionHandler.prototype.isStopEvent = function(me)
    {
        return me.getState() != null || mxEvent.isRightMouseButton(me.getEvent());
    };
    
    // Updates target terminal point for edge-to-edge connections.
    mxConnectionHandlerUpdateCurrentState = mxConnectionHandler.prototype.updateCurrentState;
    mxConnectionHandler.prototype.updateCurrentState = function(me)
    {
        mxConnectionHandlerUpdateCurrentState.apply(this, arguments);

        if (this.edgeState != null)
        {
            this.edgeState.cell.geometry.setTerminalPoint(null, false);
        
            if (this.shape != null && this.currentState != null &&
                this.currentState.view.graph.model.isEdge(this.currentState.cell))
            {
                var scale = this.graph.view.scale;
                var tr = this.graph.view.translate;
                var pt = new mxPoint(this.graph.snap(me.getGraphX() / scale) - tr.x,
                        this.graph.snap(me.getGraphY() / scale) - tr.y);
                this.edgeState.cell.geometry.setTerminalPoint(pt, false);
            }
        }
    };

    // Updates the terminal and control points in the cloned preview.
    mxEdgeSegmentHandler.prototype.clonePreviewState = function(point, terminal)
    {
        var clone = mxEdgeHandler.prototype.clonePreviewState.apply(this, arguments);
        clone.cell = clone.cell.clone();
        
        if (this.isSource || this.isTarget)
        {
            clone.cell.geometry = clone.cell.geometry.clone();
            
            // Sets the terminal point of an edge if we're moving one of the endpoints
            if (this.graph.getModel().isEdge(clone.cell))
            {
                // TODO: Only set this if the target or source terminal is an edge
                clone.cell.geometry.setTerminalPoint(point, this.isSource);
            }
            else
            {
                clone.cell.geometry.setTerminalPoint(null, this.isSource);				
            }
        }

        return clone;
    };
    
    var mxEdgeHandlerConnect = mxEdgeHandler.prototype.connect;
    mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
    {
        var result = null;
        var model = this.graph.getModel();
        var parent = model.getParent(edge);
        
        model.beginUpdate();
        try
        {
            result = mxEdgeHandlerConnect.apply(this, arguments);
            var geo = model.getGeometry(result);
            
            if (geo != null)
            {
                geo = geo.clone();
                var pt = null;
                
                if (model.isEdge(terminal))
                {
                    pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1];
                    pt.x = pt.x / this.graph.view.scale - this.graph.view.translate.x;
                    pt.y = pt.y / this.graph.view.scale - this.graph.view.translate.y;
        
                    var pstate = this.graph.getView().getState(
                            this.graph.getModel().getParent(edge));
                            
                    if (pstate != null)
                    {
                        pt.x -= pstate.origin.x;
                        pt.y -= pstate.origin.y;
                    }
                    
                    pt.x -= this.graph.panDx / this.graph.view.scale;
                    pt.y -= this.graph.panDy / this.graph.view.scale;
                }
            
                geo.setTerminalPoint(pt, isSource);
                model.setGeometry(edge, geo);
            }
        }
        finally
        {
            model.endUpdate();
        }
        
        return result;
    };
</script>
<!--  
Adds in-place highlighting for complete cell area (no hotspot).
-->
<script type="text/javascript">
    mxConnectionHandlerCreateMarker = mxConnectionHandler.prototype.createMarker;
    mxConnectionHandler.prototype.createMarker = function()
    {
        var marker = mxConnectionHandlerCreateMarker.apply(this, arguments);
        
        // Uses complete area of cell for new connections (no hotspot)
        marker.intersects = function(state, evt)
        {
            return true;
        };
        
        // Adds in-place highlighting
        mxCellHighlightHighlight = mxCellHighlight.prototype.highlight;
        marker.highlight.highlight = function(state)
        {
            if (this.state != state)
            {
                if (this.state != null)
                {
                    this.state.style = this.lastStyle;
                    
                    // Workaround for shape using current stroke width if no strokewidth defined
                    this.state.style['strokeWidth'] = this.state.style['strokeWidth'] || '1';
                    this.state.style['strokeColor'] = this.state.style['strokeColor'] || 'none';
                    
                    if (this.state.shape != null)
                    {
                        this.state.view.graph.cellRenderer.configureShape(this.state);
                        this.state.shape.redraw();
                    }
                }
                
                if (state != null)
                {
                    this.lastStyle = state.style;
                    state.style = mxUtils.clone(state.style);
                    state.style['strokeColor'] = '#00ff00';
                    state.style['strokeWidth'] = '3';
                    
                    if (state.shape != null)
                    {
                        state.view.graph.cellRenderer.configureShape(state);
                        state.shape.redraw();
                    }
                }
                
                this.state = state;
            }
        };
        
        return marker;
    };
    
    mxEdgeHandlerCreateMarker = mxEdgeHandler.prototype.createMarker;
    mxEdgeHandler.prototype.createMarker = function()
    {
        var marker = mxEdgeHandlerCreateMarker.apply(this, arguments);
        
        // Adds in-place highlighting when reconnecting existing edges
        marker.highlight.highlight = this.graph.connectionHandler.marker.highlight.highlight;
        
        return marker;
    }
</script>
<!--  
Adds oval markers for edge-to-edge connections.
-->
<script type="text/javascript">
    mxGraphGetCellStyle = mxGraph.prototype.getCellStyle;
    mxGraph.prototype.getCellStyle = function(cell)
    {
        var style = mxGraphGetCellStyle.apply(this, arguments);
        
        if (style != null && this.model.isEdge(cell))
        {
            style = mxUtils.clone(style);
            
            if (this.model.isEdge(this.model.getTerminal(cell, true)))
            {
                style['startArrow'] = 'oval';
            }
    
            if (this.model.isEdge(this.model.getTerminal(cell, false)))
            {
                style['endArrow'] = 'oval';
            }
        }
        
        return style;
    };
</script>

<script type="text/javascript">
    function ports_handler(graph){
            // 禁止拖拽连线
            // Disables floating connections (only connections via ports allowed)
            // graph.connectionHandler.isConnectableCell = function(cell)
            // {
            // return false;
            // };
            // mxEdgeHandler.prototype.isConnectableCell = function(cell)
            // {
            //     return graph.connectionHandler.isConnectableCell(cell);
            // };         
            // Disables existing port functionality
            graph.view.getTerminalPort = function(state, terminal, source)
            {
                return terminal;
            };
            // Returns all possible ports for a given terminal
			graph.getAllConnectionConstraints = function(terminal, source)
			{
				if (terminal != null && terminal.shape != null &&
					terminal.shape.stencil != null)
				{
					// for stencils with existing constraints...
					if (terminal.shape.stencil != null)
					{
						return terminal.shape.stencil.constraints;
					}
				}
				else if (terminal != null && this.model.isVertex(terminal.cell))
				{
					if (terminal.shape != null)
					{
						var ports = terminal.shape.getPorts();
						var cstrs = new Array();
						
						for (var id in ports)
						{
							var port = ports[id];
							
							var cstr = new mxConnectionConstraint(new mxPoint(port.x, port.y), port.perimeter);
							cstr.id = id;
							cstrs.push(cstr);
						}
						
						return cstrs;
					}
				}
				
				return null;
			};

			// Makes sure non-relative cells can only be connected via constraints
			graph.connectionHandler.isConnectableCell = function(cell)
			{
				if (this.graph.getModel().isEdge(cell))
				{
					return true;
				}
				else
				{
					var geo = (cell != null) ? this.graph.getCellGeometry(cell) : null;
					
					return (geo != null) ? geo.relative : false;
				}
			};
			mxEdgeHandler.prototype.isConnectableCell = function(cell)
			{
				return graph.connectionHandler.isConnectableCell(cell);
			};
			
			// Sets the port for the given connection
			graph.setConnectionConstraint = function(edge, terminal, source, constraint)
			{
				if (constraint != null)
				{
					var key = (source) ? mxConstants.STYLE_SOURCE_PORT : mxConstants.STYLE_TARGET_PORT;
					
					if (constraint == null || constraint.id == null)
					{
						this.setCellStyles(key, null, [edge]);
					}
					else if (constraint.id != null)
					{
						this.setCellStyles(key, constraint.id, [edge]);
					}
				}
			};
			
			// Returns the port for the given connection
			graph.getConnectionConstraint = function(edge, terminal, source)
			{
				var key = (source) ? mxConstants.STYLE_SOURCE_PORT : mxConstants.STYLE_TARGET_PORT;
				var id = edge.style[key];
				
				if (id != null)
				{
					var c =  new mxConnectionConstraint(null, null);
					c.id = id;
					
					return c;
				}
				
				return null;
			};

			// Returns the actual point for a port by redirecting the constraint to the port
			graphGetConnectionPoint = graph.getConnectionPoint;
			graph.getConnectionPoint = function(vertex, constraint)
			{
				if (constraint.id != null && vertex != null && vertex.shape != null)
				{
					var port = vertex.shape.getPorts()[constraint.id];
					
					if (port != null)
					{
						constraint = new mxConnectionConstraint(new mxPoint(port.x, port.y), port.perimeter);
					}
				}
				
				return graphGetConnectionPoint.apply(this, arguments);
			};
				
    }

</script>



<!-- 复制粘贴剪切删除 -->
<script type="text/javascript">
    function Clipboard_Handler(graph){
        // Public helper method for shared clipboard.
        mxClipboard.cellsToString = function(cells)
        {
            var codec = new mxCodec();
            var model = new mxGraphModel();
            var parent = model.getChildAt(model.getRoot(), 0);
            
            for (var i = 0; i < cells.length; i++)
            {
                model.add(parent, cells[i]);
            }

            return mxUtils.getXml(codec.encode(model));
        };

        // Focused but invisible textarea during control or meta key events
        var textInput = document.createElement('textarea');
        mxUtils.setOpacity(textInput, 0);
        textInput.style.width = '1px';
        textInput.style.height = '1px';
        var restoreFocus = false;
        var gs = graph.gridSize;
        var lastPaste = null;
        var dx = 0;
        var dy = 0;

        // Workaround for no copy event in IE/FF if empty
        textInput.value = ' ';
    
        // Shows a textare when control/cmd is pressed to handle native clipboard actions
        mxEvent.addListener(document, 'keydown', function(evt)
        {
            // No dialog visible
            var source = mxEvent.getSource(evt);
            
            if (graph.isEnabled() && !graph.isMouseDown && !graph.isEditing() && source.nodeName != 'INPUT')
            {
                if (evt.keyCode == 224 /* FF */ || (!mxClient.IS_MAC && evt.keyCode == 17 /* Control */) ||
                    (mxClient.IS_MAC && (evt.keyCode == 91 || evt.keyCode == 93) /* Left/Right Meta */))
                {
                    // Cannot use parentNode for check in IE
                    if (!restoreFocus)
                    {
                        // Avoid autoscroll but allow handling of events
                        textInput.style.position = 'absolute';
                        textInput.style.left = (graph.container.scrollLeft + 10) + 'px';
                        textInput.style.top = (graph.container.scrollTop + 10) + 'px';
                        graph.container.appendChild(textInput);

                        restoreFocus = true;
                        textInput.focus();
                        textInput.select();
                    }
                }
            }
        });
        
        // Restores focus on graph container and removes text input from DOM
        mxEvent.addListener(document, 'keyup', function(evt)
        {
            if (restoreFocus && (evt.keyCode == 224 /* FF */ || evt.keyCode == 17 /* Control */ ||
                evt.keyCode == 91 || evt.keyCode == 93 /* Meta */))
            {
                restoreFocus = false;
                
                if (!graph.isEditing())
                {
                    graph.container.focus();
                }
                
                textInput.parentNode.removeChild(textInput);
            }
        });
        
        // Inserts the XML for the given cells into the text input for copy
        var copyCells = function(graph, cells)
        {
            if (cells.length > 0)
            {
                var clones = graph.cloneCells(cells);
                
                // Checks for orphaned relative children and makes absolute
                for (var i = 0; i < clones.length; i++)
                {
                    var state = graph.view.getState(cells[i]);
                    
                    if (state != null)
                    {
                        var geo = graph.getCellGeometry(clones[i]);
                        
                        if (geo != null && geo.relative)
                        {
                            geo.relative = false;
                            geo.x = state.x / state.view.scale - state.view.translate.x;
                            geo.y = state.y / state.view.scale - state.view.translate.y;
                        }
                    }
                }
                
                textInput.value = mxClipboard.cellsToString(clones);
            }
            
            textInput.select();
            lastPaste = textInput.value;
        };


        
        // Handles copy event by putting XML for current selection into text input
        mxEvent.addListener(textInput, 'copy', mxUtils.bind(this, function(evt)
        {
            if (graph.isEnabled() && !graph.isSelectionEmpty())
            {
                copyCells(graph, mxUtils.sortCells(graph.model.getTopmostCells(graph.getSelectionCells())));
                dx = 0;
                dy = 0;
            }
        }));
        
        // Handles cut event by removing cells putting XML into text input
        mxEvent.addListener(textInput, 'cut', mxUtils.bind(this, function(evt)
        {
            if (graph.isEnabled() && !graph.isSelectionEmpty())
            {
                copyCells(graph, graph.removeCells());
                dx = -gs;
                dy = -gs;
            }
        }));


        // Merges XML into existing graph and layers
        var importXml = function(xml, dx, dy)
        {
            dx = (dx != null) ? dx : 0;
            dy = (dy != null) ? dy : 0;
            var cells = []

            try
            {
                var doc = mxUtils.parseXml(xml);
                var node = doc.documentElement;
                
                if (node != null)
                {
                    var model = new mxGraphModel();
                    var codec = new mxCodec(node.ownerDocument);
                    codec.decode(node, model);
                    
                    var childCount = model.getChildCount(model.getRoot());
                    var targetChildCount = graph.model.getChildCount(graph.model.getRoot());
                    
                    // Merges existing layers and adds new layers
                    graph.model.beginUpdate();
                    try
                    {
                        for (var i = 0; i < childCount; i++)
                        {
                            var parent = model.getChildAt(model.getRoot(), i);
                            
                            // Adds cells to existing layers if not locked
                            if (targetChildCount > i)
                            {
                                // Inserts into active layer if only one layer is being pasted
                                var target = (childCount == 1) ? graph.getDefaultParent() : graph.model.getChildAt(graph.model.getRoot(), i);
                                
                                if (!graph.isCellLocked(target))
                                {								
                                    var children = model.getChildren(parent);
                                    cells = cells.concat(graph.importCells(children, dx, dy, target));
                                }
                            }
                            else
                            {
                                // Delta is non cascading, needs separate move for layers
                                parent = graph.importCells([parent], 0, 0, graph.model.getRoot())[0];
                                var children = graph.model.getChildren(parent);
                                graph.moveCells(children, dx, dy);
                                cells = cells.concat(children);
                            }
                        }
                    }
                    finally
                    {
                        graph.model.endUpdate();
                    }
                }
            }
            catch (e)
            {
                alert(e);
                throw e;
            }
            
            return cells;
        };
        
        // Parses and inserts XML into graph
        var pasteText = function(text)
        {
            var xml = mxUtils.trim(text);
            var x = graph.container.scrollLeft / graph.view.scale - graph.view.translate.x;
            var y = graph.container.scrollTop / graph.view.scale - graph.view.translate.y;
            
            if (xml.length > 0)
            {
                if (lastPaste != xml)
                {
                    lastPaste = xml;
                    dx = 0;
                    dy = 0;
                }
                else
                {
                    dx += gs;
                    dy += gs;
                }
                        
                // Standard paste via control-v
                if (xml.substring(0, 14) == '<mxGraphModel>')
                {
                    graph.setSelectionCells(importXml(xml, dx, dy));
                    graph.scrollCellToVisible(graph.getSelectionCell());
                }
            }
        };
        
        // Cross-browser function to fetch text from paste events
        var extractGraphModelFromEvent = function(evt)
        {
            var data = null;
            
            if (evt != null)
            {
                var provider = (evt.dataTransfer != null) ? evt.dataTransfer : evt.clipboardData;
                
                if (provider != null)
                {
                    if (document.documentMode == 10 || document.documentMode == 11)
                    {
                        data = provider.getData('Text');
                    }
                    else
                    {
                        data = (mxUtils.indexOf(provider.types, 'text/html') >= 0) ? provider.getData('text/html') : null;
                    
                        if (mxUtils.indexOf(provider.types, 'text/plain' && (data == null || data.length == 0)))
                        {
                            data = provider.getData('text/plain');
                        }
                    }		
                }
            }
            
            return data;
        };



         // Handles paste event by parsing and inserting XML
        mxEvent.addListener(textInput, 'paste', function(evt)
        {
            // Clears existing contents before paste - should not be needed
            // because all text is selected, but doesn't hurt since the
            // actual pasting of the new text is delayed in all cases.
            textInput.value = '';

            if (graph.isEnabled())
            {
                var xml = extractGraphModelFromEvent(evt);

                if (xml != null && xml.length > 0)
                {
                    pasteText(xml);
                }
                else
                {
                    // Timeout for new value to appear
                    window.setTimeout(mxUtils.bind(this, function()
                    {
                        pasteText(textInput.value);
                    }), 0);
                }
            }

            textInput.select();
        }); 

        // Removes cells when [DELETE] is pressed
        var keyHandler = new mxKeyHandler(graph);
        keyHandler.bindKey(46, function(evt)
        {
            if (graph.isEnabled())
            {
                graph.removeCells();
            }
        });
        
    }

</script>




<script type="text/javascript">
	mxEdgeStyle.WireConnector = function(state, source, target, hints, result)
	{
		// Creates array of all way- and terminalpoints
		var pts = state.absolutePoints;
		var horizontal = true;
		var hint = null;
		
		// Gets the initial connection from the source terminal or edge
		if (source != null && state.view.graph.model.isEdge(source.cell))
		{
			horizontal = state.style['sourceConstraint'] == 'horizontal';
		}
		else if (source != null)
		{
			horizontal = source.style['portConstraint'] != 'vertical';
			
			// Checks the direction of the shape and rotates
			var direction = source.style[mxConstants.STYLE_DIRECTION];
			
			if (direction == 'north' || direction == 'south')
			{
				horizontal = !horizontal;
			}
		}
		
		// Adds the first point
		// TODO: Should move along connected segment
		var pt = pts[0];
		
		if (pt == null && source != null)
		{
			pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
		}
		else if (pt != null)
		{
			pt = pt.clone();
		}
		
		var first = pt;

		// Adds the waypoints
		if (hints != null && hints.length > 0)
		{
			// FIXME: First segment not movable
			/*hint = state.view.transformControlPoint(state, hints[0]);
			mxLog.show();
			mxLog.debug(hints.length,'hints0.y='+hint.y, pt.y)
			
			if (horizontal && Math.floor(hint.y) != Math.floor(pt.y))
			{
				mxLog.show();
				mxLog.debug('add waypoint');

				pt = new mxPoint(pt.x, hint.y);
				result.push(pt);
				pt = pt.clone();
				//horizontal = !horizontal;
			}*/
			
			for (var i = 0; i < hints.length; i++)
			{
				horizontal = !horizontal;
				hint = state.view.transformControlPoint(state, hints[i]);

				if (horizontal)
				{
					if (pt.y != hint.y)
					{
						pt.y = hint.y;
						result.push(pt.clone());
					}
				}
				else if (pt.x != hint.x)
				{
					pt.x = hint.x;
					result.push(pt.clone());
				}
			}
		}
		else
		{
			hint = pt;
		}

		// Adds the last point
		pt = pts[pts.length - 1];

		// TODO: Should move along connected segment
		if (pt == null && target != null)
		{
			pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
		}

		if (horizontal)
		{
			if (pt.y != hint.y && first.x != pt.x)
			{
				result.push(new mxPoint(pt.x, hint.y));
			}
		}
		else if (pt.x != hint.x && first.y != pt.y)
		{
			result.push(new mxPoint(hint.x, pt.y));
		}
	};
	
	mxStyleRegistry.putValue('wireEdgeStyle', mxEdgeStyle.WireConnector);
	
	// This connector needs an mxEdgeSegmentHandler
	mxGraphCreateHandler = mxGraph.prototype.createHandler;
	mxGraph.prototype.createHandler = function(state)
	{
		var result = null;
		
		if (state != null)
		{
			if (this.model.isEdge(state.cell))
			{
				var style = this.view.getEdgeStyle(state);
				
				if (style == mxEdgeStyle.WireConnector)
				{
					return new mxEdgeSegmentHandler(state);
				}
			}
		}
		
		return mxGraphCreateHandler.apply(this, arguments);
	};
</script>

<!-- 注册元件属性 -->
<!-- <script type="text/javascript">
    // Note that these XML nodes will be enclosing the
    // mxCell nodes for the model cells in the output
    var doc = mxUtils.createXmlDocument();

    var N_Mosfet = doc.createElement('N_Mos');
    // N_Mosfet.prototype.vertex =  ;

    N_Mosfet.setAttribute('Length', '100 nm');
    N_Mosfet.setAttribute('Width', '50 nm');

    // var P_Mosfet = doc.createElement('P_Mos');
    // P_Mosfet.setAttribute('Length', '100 nm');
    // P_Mosfet.setAttribute('Width', '50 nm');  

</script> -->


<!-- 添加元件图形 -->
<script type="text/javascript">
    function ResistorShape() { };
    ResistorShape.prototype = new mxCylinder();
    ResistorShape.prototype.constructor = ResistorShape;
    ResistorShape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        var dx = w / 16;

        if (isForeground)
        {
            path.moveTo(0, h / 2);
            path.lineTo(2 * dx, h / 2);
            path.lineTo(3 * dx, 0);
            path.lineTo(5 * dx, h);
            path.lineTo(7 * dx, 0);
            path.lineTo(9 * dx, h);
            path.lineTo(11 * dx, 0);
            path.lineTo(13 * dx, h);
            path.lineTo(14 * dx, h / 2);
            path.lineTo(16 * dx, h / 2);

            path.end();
        }
    };
    function N_Mosfet_Shape() { };
    N_Mosfet_Shape.prototype = new mxCylinder();
    N_Mosfet_Shape.prototype.constructor = N_Mosfet_Shape;
    N_Mosfet_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 0  , h * 1/2);
            path.lineTo(w * 3/8, h * 1/2);
            path.moveTo(w * 3/8, h * 1/4);
            path.lineTo(w * 3/8, h * 3/4);
            path.moveTo(w * 5/8, h * 0);
            path.lineTo(w * 5/8, h * 1);
            path.moveTo(w * 5/8, h * 3/4);
            path.lineTo(w * 1  , h * 3/4);            
            path.moveTo(w * 5/8, h * 1/4);
            path.lineTo(w * 1  , h * 1/4);
            path.moveTo(w * 13/16, h * 5/8);
            path.lineTo(w * 1  , h * 3/4);
            path.moveTo(w * 13/16, h * 7/8);
            path.lineTo(w * 1  , h * 3/4);            
            path.end();
        }
    };

    function P_Mosfet_Shape() { };
    P_Mosfet_Shape.prototype = new mxCylinder();
    P_Mosfet_Shape.prototype.constructor = P_Mosfet_Shape;
    P_Mosfet_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 0  , h * 1/2);
            path.lineTo(w * 3/8, h * 1/2);
            path.moveTo(w * 3/8, h * 1/4);
            path.lineTo(w * 3/8, h * 3/4);
            path.moveTo(w * 5/8, h * 0);
            path.lineTo(w * 5/8, h * 1);
            path.moveTo(w * 5/8, h * 3/4);
            path.lineTo(w * 1  , h * 3/4);            
            path.moveTo(w * 5/8, h * 1/4);
            path.lineTo(w * 1  , h * 1/4);
            // arrow
            path.moveTo(w * 6/8 , h * 3/8);
            path.lineTo(w * 5/8 , h * 1/4);
            path.moveTo(w * 6/8 , h * 1/8);
            path.lineTo(w * 5/8 , h * 1/4);            
            path.end();
        }
    };

    function Vdd_Shape() { };
    Vdd_Shape.prototype = new mxCylinder();
    Vdd_Shape.prototype.constructor = Vdd_Shape;
    Vdd_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 0  , h * 0);
            path.lineTo(w * 1  , h * 0);
            path.moveTo(w * 1/4, h * 1/2);
            path.lineTo(w * 3/4, h * 1/2);   
            path.moveTo(w * 1/2, h * 1/2);
            path.lineTo(w * 1/2, h * 1);      
            path.end();
        }
    };

    function Gnd_Shape() { };
    Gnd_Shape.prototype = new mxCylinder();
    Gnd_Shape.prototype.constructor = Gnd_Shape;
    Gnd_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 1/2, h * 0);
            path.lineTo(w * 1/2, h * 1/2);  
            path.moveTo(w * 0  , h * 1/2);
            path.lineTo(w * 1  , h * 1/2);
            path.lineTo(w * 1/2, h * 1);
            path.lineTo(w * 0  , h * 1/2);
     
            path.end();
        }
    };

    function Capacitor_Shape() { };
    Capacitor_Shape.prototype = new mxCylinder();
    Capacitor_Shape.prototype.constructor = Capacitor_Shape;
    Capacitor_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 3/8, h * 0);
            path.lineTo(w * 3/8, h * 1);  
            path.moveTo(w * 3/8, h * 1/2);
            path.lineTo(w * 0  , h * 1/2);

            path.moveTo(w * 5/8, h * 0);
            path.lineTo(w * 5/8, h * 1);
            path.moveTo(w * 5/8, h * 1/2);
            path.lineTo(w * 1  , h * 1/2); 

            path.end();
        }
    };

    function Pin_Shape() { };
    Pin_Shape.prototype = new mxCylinder();
    Pin_Shape.prototype.constructor = Pin_Shape;
    Pin_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 0  , h * 1/4);
            path.lineTo(w * 3/4, h * 1/4);
            path.lineTo(w * 1  , h * 1/2);
            path.lineTo(w * 3/4, h * 3/4);
            path.lineTo(w * 0 , h * 3/4);
            path.lineTo(w * 0 , h * 1/4);

            path.end();
        }
    };

    function Pout_Shape() { };
    Pout_Shape.prototype = new mxCylinder();
    Pout_Shape.prototype.constructor = Pout_Shape;
    Pout_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 1  , h * 1/4);
            path.lineTo(w * 1/4, h * 1/4);
            path.lineTo(w * 0  , h * 1/2);
            path.lineTo(w * 1/4, h * 3/4);
            path.lineTo(w * 1 , h * 3/4);
            path.lineTo(w * 1 , h * 1/4);

            path.end();
        }
    };

    function Vdc_Shape() { };
    Vdc_Shape.prototype = new mxCylinder();
    Vdc_Shape.prototype.constructor = Vdc_Shape;
    Vdc_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 1/2, h * 0);
            path.lineTo(w * 1/2, h * 1/8);
            path.moveTo(w * 1/2, h * 1);
            path.lineTo(w * 1/2, h * 7/8);
            // path.moveTo(w * 1/2, h * 1/8);
            // path.arcTo(3/8*w,3/8*w,180,true,true,w*1/2,h*1/2);

            path.moveTo(w * 1/2, h * 1/8);
            path.curveTo(w * 1/2, h * 1/8,
                        w * 7/8, h * 1/8, 
                        w * 7/8, h * 1/2);

            path.moveTo(w * 1/2, h * 1/8);
            path.curveTo(w * 1/2, h * 1/8,
                        w *  1/8, h * 1/8, 
                        w * 1/8, h * 1/2);
            
            path.moveTo(w * 1/2, h * 7/8);
            path.curveTo(w * 1/2, h * 7/8,
                        w * 7/8, h * 7/8, 
                        w * 7/8, h * 1/2);

            path.moveTo(w * 1/2, h * 7/8);
            path.curveTo(w * 1/2, h * 7/8,
                        w *  1/8, h * 7/8, 
                        w * 1/8, h * 1/2);

            path.moveTo(w * 1/2, h * 3/16);
            path.lineTo(w * 1/2, h * 7/16);
            path.moveTo(w * 3/8, h * 5/16);
            path.lineTo(w * 5/8, h * 5/16);
            path.moveTo(w * 3/8, h * 11/16);
            path.lineTo(w * 5/8, h * 11/16);

            path.end();
        }
    };

    function Vac_Shape() { };
    Vac_Shape.prototype = new mxCylinder();
    Vac_Shape.prototype.constructor = Vac_Shape;
    Vac_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 1/2, h * 0);
            path.lineTo(w * 1/2, h * 1/8);
            path.moveTo(w * 1/2, h * 1);
            path.lineTo(w * 1/2, h * 7/8);

            path.moveTo(w * 1/2, h * 1/8);
            path.curveTo(w * 1/2, h * 1/8,
                        w * 7/8, h * 1/8, 
                        w * 7/8, h * 1/2);

            path.moveTo(w * 1/2, h * 1/8);
            path.curveTo(w * 1/2, h * 1/8,
                        w *  1/8, h * 1/8, 
                        w * 1/8, h * 1/2);
            
            path.moveTo(w * 1/2, h * 7/8);
            path.curveTo(w * 1/2, h * 7/8,
                        w * 7/8, h * 7/8, 
                        w * 7/8, h * 1/2);

            path.moveTo( w * 1/2, h * 7/8);
            path.curveTo(w * 1/2, h * 7/8,
                         w *  1/8, h * 7/8, 
                         w * 1/8, h * 1/2);
            // +
            path.moveTo(w * 1/2, h * 3/16);
            path.lineTo(w * 1/2, h * 7/16);
            path.moveTo(w * 3/8, h * 5/16);
            path.lineTo(w * 5/8, h * 5/16);
            // -
            path.moveTo(w * 3/8, h * 11/16);
            path.lineTo(w * 5/8, h * 11/16);

            // ac
            path.moveTo( w * 2/8, h * 9/16);
            path.curveTo(w * 2/8, h * 9/16,
                        w * 2/8 , h * 8/16,
                        w * 3/8, h * 8/16);
            
            path.moveTo( w * 3/8, h * 8/16);
            path.curveTo(w * 3/8, h * 8/16,
                        w * 4/8 , h * 8/16,
                        w * 4/8 , h * 9/16);

            path.moveTo( w * 4/8, h * 9/16);
            path.curveTo(w * 4/8, h * 9/16,
                        w * 4/8 , h * 10/16,
                        w * 5/8 , h * 10/16);
            
            path.moveTo( w * 5/8, h * 10/16);
            path.curveTo(w * 5/8, h * 10/16,
                        w * 6/8 , h * 10/16,
                        w * 6/8 , h * 9/16);

            path.end();
        }
    };

    function Vsin_Shape() { };
    Vsin_Shape.prototype = new mxCylinder();
    Vsin_Shape.prototype.constructor = Vsin_Shape;
    Vsin_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 1/2, h * 0);
            path.lineTo(w * 1/2, h * 1/8);
            path.moveTo(w * 1/2, h * 1);
            path.lineTo(w * 1/2, h * 7/8);

            path.moveTo(w * 1/2, h * 1/8);
            path.curveTo(w * 1/2, h * 1/8,
                        w * 7/8, h * 1/8, 
                        w * 7/8, h * 1/2);

            path.moveTo(w * 1/2, h * 1/8);
            path.curveTo(w * 1/2, h * 1/8,
                        w *  1/8, h * 1/8, 
                        w * 1/8, h * 1/2);
            
            path.moveTo(w * 1/2, h * 7/8);
            path.curveTo(w * 1/2, h * 7/8,
                        w * 7/8, h * 7/8, 
                        w * 7/8, h * 1/2);

            path.moveTo( w * 1/2, h * 7/8);
            path.curveTo(w * 1/2, h * 7/8,
                         w *  1/8, h * 7/8, 
                         w * 1/8, h * 1/2);

            // ac
            path.moveTo( w * 2/8, h * 9/16);
            path.curveTo(w * 2/8, h * 9/16,
                        w * 2/8 , h * 8/16,
                        w * 3/8, h * 8/16);
            
            path.moveTo( w * 3/8, h * 8/16);
            path.curveTo(w * 3/8, h * 8/16,
                        w * 4/8 , h * 8/16,
                        w * 4/8 , h * 9/16);

            path.moveTo( w * 4/8, h * 9/16);
            path.curveTo(w * 4/8, h * 9/16,
                        w * 4/8 , h * 10/16,
                        w * 5/8 , h * 10/16);
            
            path.moveTo( w * 5/8, h * 10/16);
            path.curveTo(w * 5/8, h * 10/16,
                        w * 6/8 , h * 10/16,
                        w * 6/8 , h * 9/16);

            path.end();
        }
    };    

    function Vpulse_Shape() { };
    Vpulse_Shape.prototype = new mxCylinder();
    Vpulse_Shape.prototype.constructor = Vpulse_Shape;
    Vpulse_Shape.prototype.redrawPath = function(path, x, y, w, h, isForeground)
    {
        if (isForeground)
        {
            path.moveTo(w * 1/2, h * 0);
            path.lineTo(w * 1/2, h * 1/8);
            path.moveTo(w * 1/2, h * 1);
            path.lineTo(w * 1/2, h * 7/8);

            path.moveTo(w * 1/2, h * 1/8);
            path.curveTo(w * 1/2, h * 1/8,
                        w * 7/8, h * 1/8, 
                        w * 7/8, h * 1/2);

            path.moveTo(w * 1/2, h * 1/8);
            path.curveTo(w * 1/2, h * 1/8,
                        w *  1/8, h * 1/8, 
                        w * 1/8, h * 1/2);
            
            path.moveTo(w * 1/2, h * 7/8);
            path.curveTo(w * 1/2, h * 7/8,
                        w * 7/8, h * 7/8, 
                        w * 7/8, h * 1/2);

            path.moveTo( w * 1/2, h * 7/8);
            path.curveTo(w * 1/2, h * 7/8,
                         w *  1/8, h * 7/8, 
                         w * 1/8, h * 1/2);

            // pulse
            path.moveTo(w * 2/8 , h * 5/8 );
            path.lineTo(w * 2/8 , h * 3/8 );
            path.lineTo(w * 1/2 , h * 3/8 );
            path.lineTo(w * 1/2 , h * 5/8 );
            path.lineTo(w * 6/8 , h * 5/8 );
            path.lineTo(w * 6/8 , h * 3/8 );

            path.end();
        }
    };    

    mxCellRenderer.registerShape('resistor' , ResistorShape);
    mxCellRenderer.registerShape('n_mosfet' , N_Mosfet_Shape);
    mxCellRenderer.registerShape('p_mosfet' , P_Mosfet_Shape);
    mxCellRenderer.registerShape('vdd'      , Vdd_Shape);
    mxCellRenderer.registerShape('gnd'      , Gnd_Shape);
    mxCellRenderer.registerShape('capacitor', Capacitor_Shape);
    mxCellRenderer.registerShape('pin'      , Pin_Shape);
    mxCellRenderer.registerShape('pout'     , Pout_Shape);
    mxCellRenderer.registerShape('vdc'      , Vdc_Shape);
    mxCellRenderer.registerShape('vac'      , Vac_Shape);
    mxCellRenderer.registerShape('vsin'     , Vsin_Shape);
    mxCellRenderer.registerShape('vpulse'     , Vpulse_Shape);
    
    
</script>

<!-- 进行端口限制 -->
<script type="text/javascript">
    // Ports are equal for all shapes...
    var ports = new Array();
    
    // NOTE: Constraint is used later for orthogonal edge routing (currently ignored)
    ports['w'] = {x: 0, y: 0.5, perimeter: true, constraint: 'west'};
    ports['e'] = {x: 1, y: 0.5, perimeter: true, constraint: 'east'};
    ports['n'] = {x: 0.5, y: 0, perimeter: true, constraint: 'north'};
    ports['s'] = {x: 0.5, y: 1, perimeter: true, constraint: 'south'};
    ports['nw'] = {x: 0, y: 0, perimeter: true, constraint: 'north west'};
    ports['ne'] = {x: 1, y: 0, perimeter: true, constraint: 'north east'};
    ports['sw'] = {x: 0, y: 1, perimeter: true, constraint: 'south west'};
    ports['se'] = {x: 1, y: 1, perimeter: true, constraint: 'south east'};

    // Extends shapes classes to return their ports
    mxShape.prototype.getPorts = function()
    {
        return ports;
    };
    // ... except for triangles
    var ports2 = new Array();
    
    // NOTE: Constraint is used later for orthogonal edge routing (currently ignored)
    ports2['in1'] = {x: 0, y: 0, perimeter: true, constraint: 'west'};
    ports2['in2'] = {x: 0, y: 0.25, perimeter: true, constraint: 'west'};
    ports2['in3'] = {x: 0, y: 0.5, perimeter: true, constraint: 'west'};
    ports2['in4'] = {x: 0, y: 0.75, perimeter: true, constraint: 'west'};
    ports2['in5'] = {x: 0, y: 1, perimeter: true, constraint: 'west'};

    ports2['out1'] = {x: 0.5, y: 0, perimeter: true, constraint: 'north east'};
    ports2['out2'] = {x: 1, y: 0.5, perimeter: true, constraint: 'east'};
    ports2['out3'] = {x: 0.5, y: 1, perimeter: true, constraint: 'south east'};

    mxTriangle.prototype.getPorts = function()
    {
        return ports2;
    };

    //  for n mosfet
    var ports_resistor = new Array();
    ports_resistor['pos'] = {x: 0, y: 0.5, perimeter: true, constraint: 'west'};
    ports_resistor['neg'] = {x: 1, y: 0.5, perimeter: true, constraint: 'east'};

    ResistorShape.prototype.getPorts = function()
    {
        return ports_resistor;
    };

    //  for n mosfet
    var ports_nmosfet = new Array();
    ports_nmosfet['gate'] = {x: 0, y: 0.5, perimeter: true, constraint: 'west'};
    ports_nmosfet['drain'] = {x: 1, y: 0.25, perimeter: true, constraint: 'east'};
    ports_nmosfet['source'] = {x: 1, y: 0.75, perimeter: true, constraint: 'east'};

    N_Mosfet_Shape.prototype.getPorts = function(){
        return ports_nmosfet;
    };
    //  for p mosfet
    var ports_pmosfet = new Array();
    ports_pmosfet['gate'] = {x: 0, y: 0.5, perimeter: true, constraint: 'west'};
    ports_pmosfet['source'] = {x: 1, y: 0.25, perimeter: true, constraint: 'east'};
    ports_pmosfet['drain'] = {x: 1, y: 0.75, perimeter: true, constraint: 'east'};

    P_Mosfet_Shape.prototype.getPorts = function(){
        return ports_pmosfet;
    };  
    //  for vdd
    var ports_vdd = new Array();
    ports_vdd['vdd'] = {x: 0.5, y: 1, perimeter: true, constraint: 'south'};

    Vdd_Shape.prototype.getPorts = function(){
        return ports_vdd;
    };
    // for gnd
    var ports_gnd = new Array();
    ports_gnd['gnd'] = {x: 0.5, y: 0, perimeter: true, constraint: 'north'};

    Gnd_Shape.prototype.getPorts = function(){
        return ports_gnd;  
    };

    //  for capatiance
    var ports_capatiance = new Array();
    ports_capatiance['pos'] = {x: 0.0, y: 0.5, perimeter: true, constraint: 'west'};
    ports_capatiance['neg'] = {x: 1.0, y: 0.5, perimeter: true, constraint: 'east'};

    Capacitor_Shape.prototype.getPorts = function(){
        return ports_capatiance;  
    };

    //  for port_pin
    var ports_pin = new Array();
    ports_pin['pin'] = {x: 1.0, y: 0.5, perimeter: true, constraint: 'east'};

    Pin_Shape.prototype.getPorts = function(){
        return ports_pin;  
    };
    //  for port_out
    var ports_pout = new Array();
    ports_pout['pout'] = {x: 0.0, y: 0.5, perimeter: true, constraint: 'west'};

    Pout_Shape.prototype.getPorts = function(){
        return ports_pout;  
    };
    //  for Vdc
    var ports_vdc = new Array();
    ports_vdc['pos'] = {x: 0.5, y: 0.0, perimeter: true, constraint: 'north'};
    ports_vdc['neg'] = {x: 0.5, y: 1.0, perimeter: true, constraint: 'south'};

    Vdc_Shape.prototype.getPorts = function(){
        return ports_vdc;  
    };    
    //  for Vac
    var ports_vac = new Array();
    ports_vac['pos'] = {x: 0.5, y: 0.0, perimeter: true, constraint: 'north'};
    ports_vac['neg'] = {x: 0.5, y: 1.0, perimeter: true, constraint: 'south'};

    Vac_Shape.prototype.getPorts = function(){
        return ports_vac;  
    };    

    //  for Vsin
    var ports_vsin = new Array();
    ports_vsin['pos'] = {x: 0.5, y: 0.0, perimeter: true, constraint: 'north'};
    ports_vsin['neg'] = {x: 0.5, y: 1.0, perimeter: true, constraint: 'south'};

    Vsin_Shape.prototype.getPorts = function(){
        return ports_vsin;  
    };

    //  for Vsin
    var ports_vpulse = new Array();
    ports_vpulse['pos'] = {x: 0.5, y: 0.0, perimeter: true, constraint: 'north'};
    ports_vpulse['neg'] = {x: 0.5, y: 1.0, perimeter: true, constraint: 'south'};

    Vpulse_Shape.prototype.getPorts = function(){
        return ports_vpulse;  
    };

    
</script>


</head>

<!-- Calls the main function after the page has loaded. Container is dynamically created. -->
<body onload="main();">
</body>
</html>
